package control.networking;

import java.awt.Color;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;

import javax.swing.ImageIcon;

import model.*;
import model.AI.ExpertCyborg;
import model.AI.SimpleCyborg;
import model.Player.PlayerTurn;

/**
 * The master game board. All GameClients will request/post info to this.
 * RiskServer does multiple things. Primarily, it allows gamers and chatters to
 * connect and participate in a game and chat with eachother.
 * 
 * @author Kevin Luikens
 */
public class RiskServer {

	private ServerSocket serverSock;
	private Socket clientSock;

	private ObjectInputStream in;
	private ObjectOutputStream out;

	private Object connectLock;
	private Object playerLock;

	private PlayerTurnQueue queue;
	private Map currentMap = null;
	private ConnectedGamers gamersConnected;
	private GameConfig gameConfig;
	private boolean configInProcess;

	public RiskServer(int port) {
		gamersConnected = new ConnectedGamers();
		configInProcess = false;

		connectLock = new Object();
		playerLock = new Object();

		try {
			serverSock = new ServerSocket(port);
		} catch (IOException e) {
			System.out.println("Could not create a Server Socket");
		}

		System.out.println("RISK! Server ");
		System.out.println("-------------");
		run();
	}

	public void run() {
		try {
			System.out.println("Waiting...");
			clientSock = serverSock.accept();
			System.out.println(clientSock);

			RiskServerHandler handler = new RiskServerHandler(clientSock, this);
			gamersConnected.addPlayer(handler);
			new Thread(handler).start();

			synchronized (connectLock) {
				connectLock.wait();
			}
			System.out.println("new RiskServerHandler started: #"
					+ gamersConnected.getSize());

		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("gameConfig should not be null -- " + gameConfig);

		if (gameConfig != null) { // Wait for all the humans to connect
			while (gamersConnected.getSize() < gameConfig.getTotalHumans()) {
				System.out.println(gamersConnected.getSize() + "/"
						+ gameConfig.getTotalHumans());
				connect();
			}
		}
		
		// Everyone's connected and ready, let's initialize.
		synchronized (playerLock) {
			try {
				playerLock.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();	}
		}
		
		initializeGame();
	}

	/**
	 * Is the main logic that handles connections from clients and 
	 * spawns server handlers from such.
	 */
	private void connect() {
		try {
			System.out.println("Waiting...");
			clientSock = serverSock.accept();
			System.out.println(clientSock);
		} catch (IOException e) {
			e.printStackTrace();
		}

		RiskServerHandler handler = new RiskServerHandler(clientSock, this);
		gamersConnected.addPlayer(handler);
		Thread toAdd = new Thread(handler); 
		toAdd.start();
		System.out.println("new RiskServerHandler started: #"
				+ gamersConnected.getSize());
	}

	/**
	 * Returns the master Map instance that the game is based on.
	 * @return Map object
	 */
	public Map getMap() {
		return currentMap;
	}

	/**
	 * Will update the master Map with a current one that has a new 
	 * modification.
	 * @param o Object instance
	 */
	public void updateWithNewMap(Object o) {
		this.broadcast(o);
	}

	/**
	 * Notifies a lock held locally to unlock and allow subsequent action to
	 * take place.
	 */
	public void notifyPlayerSent() {
		synchronized (playerLock) {
			playerLock.notify();
		}
	}

	/**
	 * Broadcasts the objects to all of the other gamers connected so that
	 * they may update their client with the current Map.
	 * @param o Object instance
	 */
	private void broadcast(Object o) {
		gamersConnected.broadcast(o);
		System.out.println("Server Broadcast " + o);
	}

	/**
	 * Receives the game configuration that first player to connect creates,
	 * setting the parameters for the RISK game.
	 * @param gc
	 */
	public void setGameConfig(GameConfig gc) {
		gameConfig = gc;
		System.out.println("RiskServer: received GameConfig object");
		System.out.println("GameConfig: " + gameConfig);
		synchronized (connectLock) {
			connectLock.notifyAll();
		}
	}

	/**
	 * Called privately when the game is ready to be initialized. Loads
	 * the Human and AI players, and spawns the ChatServer.
	 */
	private void initializeGame() {
		System.out.println("RiskServer: inside initializeGame()");
		
		// Spawn AI clients here to fill the void
		for (int i = gamersConnected.getPlayerList().size(); i < gameConfig.getNumSimpleAI()+ gameConfig.getTotalHumans(); i++) {
			
			SimpleCyborg p = new SimpleCyborg("Simple - AI", Color.CYAN, new ImageIcon("images/players/Wall-E.png"));
			
			System.out.println("RiskServer: SC -- " + p);
			if (i == 1) {
				p.setColor(Color.ORANGE);
			} else if (i == 2) {
				p.setColor(Color.PINK);
			} else if (i == 3) {
				p.setColor(Color.YELLOW);
			} else if (i == 4) {
				p.setColor(Color.RED);
			} else if (i == 5) {
				p.setColor(Color.CYAN);
			} else if (i == 6) {
				p.setColor(Color.LIGHT_GRAY);
			}
			
			RiskAIClient newAI = new RiskAIClient(p, 4001);
			newAI.start();
			try {
				Socket aiClient = serverSock.accept();
				RiskServerHandler handler = new RiskServerHandler( aiClient, this );
				handler.setPlayer( p );
				gamersConnected.addPlayer( handler );
				Thread toAdd = new Thread(handler); 
				toAdd.start();
			} catch (IOException e1) {
				e1.printStackTrace();	} 

			System.out.println("Spawned a new Simple AI handler");
		}
		
		for (int i = gamersConnected.getPlayerList().size(); i < gameConfig.getTotalPlayers(); i++) {

			ExpertCyborg p = new ExpertCyborg("Expert - AI", Color.CYAN, new ImageIcon("images/players/HAL.png"));

			if (i == 1) {
				p.setColor(Color.ORANGE);
			} else if (i == 2) {
				p.setColor(Color.PINK);
			} else if (i == 3) {
				p.setColor(Color.YELLOW);
			} else if (i == 4) {
				p.setColor(Color.RED);
			} else if (i == 5) {
				p.setColor(Color.CYAN);
			} else if (i == 6) {
				p.setColor(Color.LIGHT_GRAY);
			}
			
			RiskAIClient newAI = new RiskAIClient(p, 4001);
			newAI.start();
			try {
				Socket aiClient = serverSock.accept();
				RiskServerHandler handler = new RiskServerHandler( aiClient, this );
				handler.setPlayer( p );
				gamersConnected.addPlayer( handler );
				Thread toAdd = new Thread(handler); 
				toAdd.start();
			} catch (IOException e1) {
				e1.printStackTrace();	} 

			System.out.println("Spawned a new Expert AI handler");
		}

		System.out.println("RiskServer: before queue is assigned");
		queue = new PlayerTurnQueue(gamersConnected.getPlayerList(), gameConfig
				.getTotalPlayers());

		gamersConnected.getPlayerList().get(0).setTurnPhase(PlayerTurn.START);
		currentMap = new ClassicMap(gamersConnected.getPlayerList());
		if(gameConfig.isRandomAssign()){
			currentMap.assignRulers();
		}
		new ChatServer().start();
		broadcast(currentMap);
		System.out.println("RiskServer: just broadcasted " + currentMap);
	}

	/**
	 * Checks whether the process of starting a game has begun. This is done to prevent
	 * another gamer connecting and starting their own configuration while another gamer
	 * is already in the process of doing such.
	 * @return boolean
	 */
	public boolean getConfigInProcess() {
		return configInProcess;
	}

	/**
	 * Sets the configuration class for the RISK game.
	 * @param b
	 */
	public void setConfigInProcess(boolean b) {
		configInProcess = b;
	}

	/**
	 * Advances the turn queue on the server and broadcasts the next
	 * player to the gamers to notify them of whose turn it is.
	 */
	public void advanceQueue() {
		gamersConnected.broadcast(queue.getNextPlayer());
	}

	/**
	 * Gets the list of gamers who are connected and playing. Includes AI.
	 * @return List<Player>
	 */
	public List<Player> getConnectedGamerList() {
		return gamersConnected.getPlayerList();
	}

	// =========================================

	/**
	 * Main method that starts the server and subsequent magical process.
	 */
	public static void main(String[] args) {
		new RiskServer(4001);
	}

}
